Best way to structure the code for an ASP.NET MVC REST API that is decoupled from the data formats? - asp.net-mvc

I am creating a REST API in ASP.NET MVC. I want the format of the request and response to be JSON or XML, however I also want to make it easy to add another data format and easy to create just XML first and add JSON later.
Basically I want to specify all of the inner workings of my API GET/POST/PUT/DELETE requests without having to think about what format the data came in as or what it will leave as and I could easily specify the format later or change it per client. So one guy could use JSON, one guy could use XML, one guy could use XHTML. Then later I could add another format too without having to rewrite a ton of code.
I do NOT want to have to add a bunch of if/then statements to the end of all my Actions and have that determine the data format, I'm guessing there is some way I can do this using interfaces or inheritance or the like, just not sure the best approach.

Serialization
The ASP.NET pipeline is designed for this. Your controller actions don't return the result to the client, but rather a result object (ActionResult) which is then processed in further steps in the ASP.NET pipeline. You can override the ActionResult class. Note that FileResult, JsonResult, ContentResult and FileContentResult are built-in as of MVC3.
In your case, it's probably best to return something like a RestResult object. That object is now responsible to format the data according to the user request (or whatever additional rules you may have):
public class RestResult<T> : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
string resultString = string.Empty;
string resultContentType = string.Empty;
var acceptTypes = context.RequestContext.HttpContext.Request.AcceptTypes;
if (acceptTypes == null)
{
resultString = SerializeToJsonFormatted();
resultContentType = "application/json";
}
else if (acceptTypes.Contains("application/xml") || acceptTypes.Contains("text/xml"))
{
resultString = SerializeToXml();
resultContentType = "text/xml";
}
context.RequestContext.HttpContext.Response.Write(resultString);
context.RequestContext.HttpContext.Response.ContentType = resultContentType;
}
}
Deserialization
This is a bit more tricky. We're using a Deserialize<T> method on the base controller class. Please note that this code is not production ready, because reading the entire response can overflow your server:
protected T Deserialize<T>()
{
Request.InputStream.Seek(0, SeekOrigin.Begin);
StreamReader sr = new StreamReader(Request.InputStream);
var rawData = sr.ReadToEnd(); // DON'T DO THIS IN PROD!
string contentType = Request.ContentType;
// Content-Type can have the format: application/json; charset=utf-8
// Hence, we need to do some substringing:
int index = contentType.IndexOf(';');
if(index > 0)
contentType = contentType.Substring(0, index);
contentType = contentType.Trim();
// Now you can call your custom deserializers.
if (contentType == "application/json")
{
T result = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(rawData);
return result;
}
else if (contentType == "text/xml" || contentType == "application/xml")
{
throw new HttpException(501, "XML is not yet implemented!");
}
}

Just wanted to put this on here for the sake of reference, but I have discovered that using ASP.NET MVC may not be the best way to do this:
Windows Communication Foundation (WCF)
provides a unified programming model
for rapidly building service-oriented
applications that communicate across
the web and the enterprise
Web application developers today are
facing new challenges around how to
expose data and services. The cloud,
move to devices, and shift toward
browser-based frameworks such as
jQuery are all placing increasing
demands on surfacing such
functionality in a web-friendly way.
WCF's Web API offering is focused on
providing developers the tools to
compose simple yet powerful
applications that play in this new
world. For developers that want to go
further than just exposing over HTTP,
our API will allow you to access all
the richness of HTTP and to apply
RESTful constraints in your
application development. This work is
an evolution of the HTTP/ASP.NET AJAX
features already shipped in .Net 4.0.
http://wcf.codeplex.com/
However I will not select this as the answer because it doesn't actually answer the question despite the fact that this is the route I am going to take. I just wanted to put it here to be helpful for future researchers.

Related

Breeze.Sharp Cannot Import Metadata from Breeze Server for ASP.NET Core 3

I am trying to use breeze sharp with Blazor Webassembly.
I have been able to recompile breeze sharp for .Net Standard 2.0 and 2.1 that has shown positive results reading data from the server.
However when trying to load Metadata from script I have noticed that MetadaStore.ImportMetadata method fails to load metadata.
When I followed up I found that there Breeze.Sharp expects metadataVersion property and namingConvention node from the metadata json returned by the breeze server.
What I can see is that First the metadata format has changed and those properties are no longer present in the generated metadata.
But if I FetchMetadata using DataService then the metadata is loaded into the metadatastore.
My Question is ...Is there a plan to update breeze sharp to be aligned with recent developments in the dotnet platform?
Kindly consider it as it aligns very well with Blazor. Actually my experiment went very fine expect for loading metadata stored locally instead of loading it from the server every time.
private void DeserializeFrom(JNode jNode, bool isFromServer) {
MetadataVersion = jNode.Get<String>("metadataVersion");
// may be more than just a name
var ncNode = jNode.GetJNode("namingConvention");
if (ncNode != null) {
var nc = Configuration.Instance.FindOrCreateNamingConvention(ncNode);
if (nc == null) {
OnMetadataMismatch(null, null, MetadataMismatchTypes.MissingCLRNamingConvention, ncNode.ToString());
} else {
// keep any preexisting ClientServerNamespaceMap info
NamingConvention = nc.WithClientServerNamespaceMapping(this.NamingConvention.ClientServerNamespaceMap);
}
}
// localQueryComparisonOptions
jNode.GetJNodeArray("dataServices").Select(jn => new DataService(jn)).ForEach(ds => {
if (GetDataService(ds.ServiceName) == null) {
AddDataService(ds);
}
});
jNode.GetJNodeArray("structuralTypes")
.ForEach(jn => UpdateStructuralTypeFromJNode(jn, isFromServer));
jNode.GetMap<String>("resourceEntityTypeMap").ForEach(kvp => {
var stName = kvp.Value;
if (isFromServer) {
stName = TypeNameInfo.FromStructuralTypeName(stName).ToClient(this).StructuralTypeName;
}
// okIfNotFound because metadata may contain refs to types that were already excluded earlier in
// UpdateStructuralTypeFromJNode
var et = GetEntityType(stName, true);
if (et != null) {
SetResourceName(kvp.Key, et);
}
});
}
I have been following up very closely new developments and this is what I have noticed so far.
Microsoft is reviving the product called Microsoft Restier.
First it was developed on the .Net Framework Platform but now they are rewriting it to run on the .Net Core and is expected to go RTM in the first half 0f the year 2020. A good thing about this Microsoft Restier is that your Full Entity Context is exposed as OData endpoint with your entity lists exposed without the need for creating Controllers on Actions.
This way it reduces the tedious work of writing an action for each entity that you want to expose as an OData resource different from the current case with plain OData and Breeze Server.
There are so many areas for configurations and extensibility.
Microsoft is remaking RIA Data Services in the form of OData Connected Service.
This creates a Proxy in the same way as RIA Data Services.
With the Proxy generated there is no need to create a client data model. The model created on the server will suffice... not as the case for breeze sharp where you need to create a model on the client although there are signs that at DevForce they are exploring using PostSharp to make it possible to use POCO objects sharable between breeze server and clients. We however do not know when will this be available.
OData Connected Service works seamlessly with Blazor Server (On the Client Side there some bugs that are being worked on ) and removes the headache of working with bare bone HttpClient.
As for breeze sharp it currently works well with both Blazor Server and Web Assembly versions (Breeze Sharp Standard). I think they will rework their product and enable it to work with OData the same way Breezejs does. This combined with Microsoft.Restier will make life very easy.
Actually breeze has very nice features especially when it comes to caching.
Lets wait and see.

What is the alternative for jsonp format in MVC core?

I need to return result in jsonp format while I'm using asp.net MVC Core.
In asp.net MVC we can use MVC.jsonp dll an it's work fine but what is the alternative in MVC Core because I can't find any.
public JsonpResult FunctionalitesTblList()
{
var settings = new JsonSerializerSettings();
return Jsonp(Rows, settings);
}
There was no built in ability to handle JSONP with MVC, so you were always using a third-party addition. Apparently, that library is incompatible with .NET Core. Therefore, your option is to find a similar library that is compatible or choose some other approach.
#CuongLe is correct that CORS is a better approach overall, so you should definitely investigate that. However, if you insist on JSONP, it's so simple to implement manually, you don't really need a library.
Simply, all JSONP is a is JSON passed into a "callback" function, specified by the client. In other words, if the callack was specified as "mycallback", the response should just look like:
mycallback({ "foo": "bar" });
As a result, your code simply becomes:
string json = null;
using (var ms = new MemoryStream())
{
var serializer = new DataContractJsonSerializer(typeof(Foo));
serializer.WriteObject(ms, foo);
json = Encoding.UTF8.GetString(ms.ToArray());
}
var jsonp = String.Format("{0}({1});", callback, json);
Response.ContentType = "application/javascript";
return Content(jsonp);

mvc4 acting as a gateway

what i want to achieve is:
a central server connected to the database, using entity framework
a server who for some reason can't reach the database but forward the requests to the central server (not all of them only the one who require the database)
some httpclients who can't reach the central server nor the database but only the middle server
I've already tried with success modifying the controller method to create an http client who redo the reuest to the cenral server, but that seems the worst way to me, especially because i've lots of controllers and methods
public User GetUser(int id)
{
if (Properties.Settings.Default.SyncEnabled)
{
System.Net.Http.HttpClient httpClient = new System.Net.Http.HttpClient();
httpClient.BaseAddress = Properties.Settings.Default.SyncAddress;
var result = httpClient.GetAsync(this.Request.Url.PathAndQuery).Result;
return this.Content(result.Content.ReadAsStringAsync().Result, result.Content.Headers.ContentType.MediaType);
}
else
{
User user = DbContext.Users.Find(id);
user.LastOnline = DateTime.Now;
DbContext.SaveChanges();
return user;
}
}
i was thinking about using register route, but i'd like to know if it's a good idea, before reading how routes works...
i'm also intrested in how would you implement that.
Since nobody gave me an answer i'm gonna suggest myself to try with this:
http://www.iis.net/learn/extensions/url-rewrite-module/reverse-proxy-with-url-rewrite-v2-and-application-request-routing
seems that reverse proxy can do the trick, but i need to do it with two separate iis

Using the Json.NET serializer in an MVC4 project

I'm starting to learn Json.NET, but I'm having trouble using its serializer. I have a new MVC4 project with a Web.API service:
public class PTE_TestsController : ApiController {
PTE_TestsRepository _repository = new PTE_TestsRepository();
// GET /api/PTE_Tests/5
public HttpResponseMessage<string> Get(int id) {
try {
PTE_Test test = _repository.GetTest(id);
return new HttpResponseMessage<string>(JsonConvert.SerializeObject(test));
} catch {
return new HttpResponseMessage<string>(HttpStatusCode.NotFound);
}
}
}
JsonConvert.SerializeObject() works as expected and returns a string. My Web.API controller returns that as part of an HttpResponseMessage. The end result, when viewed in Fiddler, is not JSON data, but JSON data being serialized again (I think):
"{\"ID\":1,\"Name\":\"Talar Tilt\",\"TagID\":1,\"PracticeID\":1,
\"SpecificAreaID\":1,\"TypeID\":1,\"VideoID\":1,\"PicID\":1}"
Does someone know how to turn off the default serializer so I can use Json.NET directly? By the way, I'm not using the default serializer because I can't figure out how to make it work with complex objects (PTE_Test will eventually contain members of type List).
Alternately, it will also solve my problem if someone can explain how to use the default serializer with complex objects. MSDN's explanation didn't help me.
Rick Strahl has a blog on that here with a code that works.
As others have pointed out, you need to create a formatter and replace the DataContractSerializer with the JSON.NET serializer. Although, if you're not in a rush for JSON.NET specifically, rumor has it that next beta/rc is going to have support for JSON.NET built in.
Conceptually, however, you're missing part of the magic of WebAPI. With WebAPI you return your object in it's native state (or IQueryable if you want OData support). After your function call finishes the Formatter's take over and convert it into the proper shape based on the client request.
So in your original code, you converted PTE_Test into a JSON string and returned it, at which point the JSON Formatter kicked in and serialized the string. I modified your code as follows:
public class PTE_TestsController : ApiController {
PTE_TestsRepository _repository = new PTE_TestsRepository();
public HttpResponseMessage<PTE_Test> Get(int id) {
try {
PTE_Test test = _repository.GetTest(id);
return new HttpResponseMessage(test);
} catch {
return new HttpResponseMessage<string>(HttpStatusCode.NotFound);
}
}
}
Notice how your function returns PTE_Test instead of string. Assuming the request came in with a request header of Accept = application/json then the JSON formatter will be invoked. If the request had a header of : Accept = text/xml the XML formatter is invoked.
There's a decent article on the topic here. If you're a visual learner, Scott Gu shows some examples using fiddler in this video, starting around 37 minutes. Pedro Reys digs a little deeper into content negotiation here.
The way to do this is to use formatters.
Check out: https://github.com/WebApiContrib/WebAPIContrib/tree/master/src/WebApiContrib.Formatting.JsonNet.
Json.NET support will be in the RC release of Web API.

How do I support ETags in ASP.NET MVC?

How do I support ETags in ASP.NET MVC?
#Elijah Glover's answer is part of the answer, but not really complete. This will set the ETag, but you're not getting the benefits of ETags without checking it on the server side. You do that with:
var requestedETag = Request.Headers["If-None-Match"];
if (requestedETag == eTagOfContentToBeReturned)
return new HttpStatusCodeResult(HttpStatusCode.NotModified);
Also, another tip is that you need to set the cacheability of the response, otherwise by default it's "private" and the ETag won't be set in the response:
Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate);
So a full example:
public ActionResult Test304(string input)
{
var requestedETag = Request.Headers["If-None-Match"];
var responseETag = LookupEtagFromInput(input); // lookup or generate etag however you want
if (requestedETag == responseETag)
return new HttpStatusCodeResult(HttpStatusCode.NotModified);
Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate);
Response.Cache.SetETag(responseETag);
return GetResponse(input); // do whatever work you need to obtain the result
}
ETAG's in MVC are the same as WebForms or HttpHandlers.
You need a way of creating the ETAG value, the best way I have found is using a File MD5 or ShortGuid.
Since .net accepts a string as a ETAG, you can set it easily using
String etag = GetETagValue(); //e.g. "00amyWGct0y_ze4lIsj2Mw"
Response.Cache.SetETag(etag);
Video from MIX, at the end they use ETAG's with REST

Resources