How to have "service" pages in ASP.NET MVC? - c#

MVC newbie here:
I've more or less worked out the page navigation aspect of MVC. But let's say I don't want to navigate to a View, but rather I want to get a response out of the web site, e.g. by sending a request to http://mysite.com/Services/GetFoo/123 I want to make a database request to select a Foo object with ID 123 and return it serialized as XML.
How do you do that?

I would write a custom action result:
public class XmlResult : ActionResult
{
private readonly object _data;
public XmlResult(object data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
_data = data;
}
public override void ExecuteResult(ControllerContext context)
{
// You could use any XML serializer that fits your needs
// In this example I use XmlSerializer
var serializer = new XmlSerializer(_data.GetType());
var response = context.HttpContext.Response;
response.ContentType = "text/xml";
serializer.Serialize(response.OutputStream, _data);
}
}
and then in my controller:
public ActionResult GetFoo(int id)
{
FooModel foo = _repository.GetFoo(id);
return new XmlResult(foo);
}
And if this return new XmlResult(foo); feels ugly to your eyes, you could have an extension method:
public static class ControllersExtension
{
public static ActionResult Xml(this ControllerBase controller, object data)
{
return new XmlResult(data);
}
}
and then:
public ActionResult GetFoo(int id)
{
FooModel foo = _repository.GetFoo(id);
return this.Xml(foo);
}

Sounds like you want to create a REST API.
Have a look at Siesta which will do all the heavy lifting.
Alternatively you could write an action method which returns a view which renders as XML rather than HTML.
Something like:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<MyModel>" ContentType="text/xml" %>
<%= SerializationHelper.SerializeAsXml(Model) %>

If you could live with a JSON result, the following should work:
public class ServicesController : Controller
{
public ActionResult GetFoo(int id)
{
var dbResult = SomeDbUtil.GetFoo(id);
return this.Json(dbResult);
}
}
This would give you pretty a basic JSON query result. However, if you want your services to be discoverable SOAP XML services etc., setting up another project/website that acts as the web service would seem to be the better idea to me.

You can probably find an answer to your question here:
See Return XML from a controller's action in as an ActionResult?

Related

Asp.net core Cleanest way to return View or Json/XML

In asp.net core I would like to set up my API controller to do the following:
by default return View(model);
/api/id.json to return model; as json
/api/id.xml to return model; as xml
The second two can be achieved by using the [FormatFilter] see here
[FormatFilter]
public class ProductsController
{
[Route("[controller]/[action]/{id}.{format?}")]
public Product GetById(int id)
However this requires the method to return an object and not a View(object). Is there anyway to cleanly support also returning Views?
You cannot do both in the same action. However, you can factor out the common functionality into a private method and then implement two actions with minimal code duplication:
[Route("[controller]")]
[FormatFilter]
public class ProductsController : Controller
{
private Product GetByIdCore(int id)
{
// common code here, return product
}
[HttpGet("[action]/{id}")]
[ActionName("GetById")]
public IActionResult GetByIdView(int id) => View(GetByIdCore(id));
[HttpGet("[action]/{id}.{format}")]
public Product GetById(int id) => GetByIdCore(id);
}
It's necessary to use different action names here, because the method signatures cannot differ merely on return type. However, the [ActionName] attribute can be used as above to make them appear to have the same name for the purposes of URL generation and such.
You can actually achieve this just using the one action. Here's an example of how I got it to work:
[FormatFilter]
public class ProductsController : Controller
{
[Route("[controller]/[action]/{id}.{format?}")]
public IActionResult GetById(int id, string format)
{
var yourModel = ...;
if (string.IsNullOrWhiteSpace(format))
return View(yourModel);
return Ok(yourModel);
}
By using IActionResult as the return type, you can return either a ViewResult or an OkObjectResult. You can get access to the format value by taking it as a parameter in your action, check if it's empty and then react accordingly.
I also added Controller as the base class in order to access the convenience methods for creating the relevant results (View(...) and Ok(...)).
If you're going to be using this pattern a lot, to keep your controllers as clean as possible, you could create a base class that exposed a "FormatOrView" method:
[FormatFilter]
public abstract class FormatController : Controller
{
protected ActionResult FormatOrView(object model)
{
var filter = HttpContext.RequestServices.GetRequiredService<FormatFilter>();
if (filter.GetFormat(ControllerContext) == null)
{
return View(model);
}
else
{
return new ObjectResult(model);
}
}
}
And then your controller can inherit from this and use the FormatOrView method
public class ProductsController : FormatController
{
[Route("[controller]/[action]/{id}.{format?}")]
public ActionResult GetById(int id)
{
var product = new { Id = id };
return FormatOrView(product);
}
}
Edit to list final accepted answer by GreyCloud: Here is a generic slightly simplified method you can put into a controller (or make an extension method or put into an abstract base class as above). Note the ?. in case the service is not defined for some reason.
private ActionResult<T> FormatOrView<T>(T model) {
return HttpContext.RequestServices.GetRequiredService<FormatFilter>()?.GetFormat(ControllerContext) == null
? View(model)
: new ActionResult<T>(model);
}
The FormatFilter is part of the content negotiation of your app, in AspNetCore, you have the control to handle your input or output formatters also on the ConfigureServices where you have more control, even you can add more media types there
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options .OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
options .InputFormatters.Add(new XmlDataContractSerializerInputFormatter(options ));
//more output formatters
var jsonOutputFormatter = options.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
if (jsonOutputFormatter != null)
{
jsonOutputFormatter.SupportedMediaTypes.Add("application/vnd.myvendormediatype");
}
}
}
But going back on the content negotiation in your controllers you can keep just one. The only thing is that you need to know the mediaType to return your View or your json content. Only be sure to pass an accept header with the content type you want. With the content type you are defining for an api or for an mvc application which is the content/format the client should expect
[HttpGet("[action]/{id}")]
public IActionResult public Product GetById(int id, [FromHeader(Name = "Accept")] string mediaType)
{
if (mediaType == "application/vnd.myvendormediatype")
{
var data = GetYourData(...)
return Json(data);
}
else return View("YourDefaultView");
}

ASP.NET Core - acessing object from razor in code behind

I'm new to ASP.NET Core, so I'm still trying to understand it.
I was trying to access an object from code behind. Is it possible? I have been trying and searching for hours and I have yet to understand it. I'll leave some of what I've tried bellow, which apparently isn't working. If I could get some insight on this I'd very much appreciate it.
In classic ASP.NET I always to the id approach from code behind to the client side. But now I've been asked to try a different approach. In this case, a loop. How can I do this? I've also been reading the microsoft documentation but I still can't understand this. I'd appreciate some help.
Here's a spinnet of what I tried:
// The controller
public class HomeController : Controller
{
public GetInfo GetInfo { get; set; }
public IActionResult Offers()
{
GetInfo = new GetInfo();
GetInfo.GetOffers();
return View();
}
}
// The GetInfo class which gets data from a JSON file
public class GetInfo
{
public Offer.RootObject Offers { get; set; }
public void GetOffers()
{
var client = new RestClient("whatever.com");
// client.Authenticator = new HttpBasicAuthenticator(username, password);
var request = new RestRequest("Home/GetOffersJson", Method.GET);
IRestResponse response = client.Execute(request);
var content = response.Content;
var obj = JsonConvert.DeserializeObject<Offer.RootObject>(content);
Offers = new Offer.RootObject
{
total = obj.total,
data = obj.data
};
}
}
// The View file, where I'm trying to access the object from c#, which supposedly is 'loaded'
#model HostBookingEngine_HHS.Models.GetInfo;
#{
ViewData["Title"] = "Offers";
}
#foreach (var item in Model.Offers.data)
{
<span asp-validation-for="#item.TextTitle"></span>
}
Thanks, in advance.
asp.net core view has four over loading these are
public virtual ViewResult View();
public virtual ViewResult View(string viewName, object model);
public virtual ViewResult View(object model);
public virtual ViewResult View(string viewName);
ASP.NET Core can use all of these with exact parameter
You can pass your model object using Public Virtual ViewResult View(object model);
You need to pass your model object like this
return View(GetInfo);
which actually access your parameter object
#model HostBookingEngine_HHS.Models.GetInfo;
Also there are several method of passing data to view
Like ViewBag,ViewData which also grab the value in each request.
To have Offers available on view, you have to pass model to a view as an argument to View() method. So in your case you have to make your action look like:
public IActionResult Offers()
{
GetInfo = new GetInfo();
GetInfo.GetOffers();
return View(GetInfo);
}

Turning URL into controller / action pair

In .Net MVC, you define routes into a RouteCollection. The URL helper methods make it easy to turn a controller + action + optional params into a URL.
When .Net MVC processes a request from a client browser, it clearly maps this URL to the right controller + action, to execute the appropriate command.
However, I can't see a way to programatically access this routing on the fly, such that I can turn a fully qualified URL (or a list of 10k+ URLs) into it's route components.
Does anyone know how you'd turn, for example, the following string input:
"http://stackoverflow.com/questions/2342325/c-sharp-net-mvc-turning-url-into-controller-action-pair"
into the following output:
{
controller: "questions",
action: "view",
id: 2342325,
seoText: "c-sharp-net-mvc-turning-url-into-controller-action-pair"
}
Given this mapping is clearly being done by .Net, is it exposed anywhere?
Why would anyone want to do this?
Imagine you have a list of URLs you know have been accessed, mostly dynamic in nature, for example stackoverflow.com/questions/2342325/c-sharp-net-mvc-turning-url-into-controller-action-pair, and you want to work out which actual endpoints / actions / controllers are being hit programatically (without much care about the actual data being passed).
You could hand code mappings, such that you know /questions/{id}/{text} -> controller: questions, action: question, but that's not future-proof, nor is it fun, and relies on text manipulation / processing.
Given a Route Dictionary and a list of URLs, with a function as described above, you could look at which controllers are most hit, or which actions, etc.
You should take a look at creating your own MvcRouteHandler. This is the point in the MVC stack where the Route Engine has already parsed the URL to find which Controller and Action to call, and then it goes through this method to get the actual C# class and method to invoke. No authorization or even HTTP Verb has been applied yet, so you will see every call that is made to your application.
public class CustomRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext context)
{
var controller = context.RouteData.Values["controller"];
var action = context.RouteData.Values["action"];
// Do whatever logging you want with this data, maybe grab the other params too.
return base.GetHttpHandler(context);
}
}
This can easily be registered where you set up your Routing.
routes.MapRoute("Home", "{controller}/{action}", new
{
controller = "Home",
action = "Index"
})
.RouteHandler = new CustomRouteHandler();
Looks like the only way to do this is by creating a dummy HTTP Context, similar to how you would unit test routes. It's a shame MVC doesn't provide better access to this, given it's being run on every request, rather than wrapping it up inside the context object.
Anyway, here is a working solution which can be modified to suit your needs:
public class UrlToRouteMapper
{
public static RouteValueDictionary GetRouteDataFromURL(string absoluteURL)
{
var testUrl = "~" + new Uri(absoluteURL).AbsolutePath;
var context = new StubHttpContextForRouting(requestUrl: testUrl);
     var routes = new System.Web.Routing.RouteCollection();
     MvcApplication.RegisterRoutes(routes);
System.Web.Routing.RouteData routeData = routes.GetRouteData(context);
return routeData.Values;
}
public static string GetEndpointStringFromURL(string absoluteURL)
{
var routeData = GetRouteDataFromURL(absoluteURL);
return routeData["controller"] + "/" + routeData["action"];
}
}
public class StubHttpContextForRouting : HttpContextBase {
StubHttpRequestForRouting _request;
StubHttpResponseForRouting _response;
public StubHttpContextForRouting(string appPath = "/", string requestUrl = "~/") {
_request = new StubHttpRequestForRouting(appPath, requestUrl);
_response = new StubHttpResponseForRouting();
}
public override HttpRequestBase Request {
get { return _request; }
}
public override HttpResponseBase Response {
get { return _response; }
}
}
public class StubHttpRequestForRouting : HttpRequestBase {
string _appPath;
string _requestUrl;
public StubHttpRequestForRouting(string appPath, string requestUrl) {
_appPath = appPath;
_requestUrl = requestUrl;
}
public override string ApplicationPath {
get { return _appPath; }
}
public override string AppRelativeCurrentExecutionFilePath {
get { return _requestUrl; }
}
public override string PathInfo {
get { return ""; }
}
}
public class StubHttpResponseForRouting : HttpResponseBase {
public override string ApplyAppPathModifier(string virtualPath) {
return virtualPath;
}
}

ASP.NET MVC JSON body with POST

I am new to ASP.NET MVC and learning. So far I have figured out how I can create a JSON Object and return that as a response to a request. However, I'm not able to pass a JSON body as part of a POST request like I normally did using Java.
Here is the code how I did this there -
#Path("/storeMovement")
#POST
#Consumes("application/json")
#Produces("application/json")
public String storeTrace(String json) {
JSONObject response = new JSONObject();
JSONParser parser = new JSONParser();
String ret = "";
try {
Object obj = parser.parse(json);
JSONObject jsonObj = (JSONObject) obj;
RecordMovement re = new RecordMovement((double) jsonObj.get("longitude"), (double) jsonObj.get("latitude"), (long) jsonObj.get("IMSI"));
ret = re.Store();
// Clear object
re = null;
System.gc();
response.put("status", ret);
} catch (Exception e) {
response.put("status", "fail " + e.toString());
}
return response.toJSONString();
}
I tried the same in the ASP.NET Action method but the value in the string parameter a is null as seen while debugging. Here's the code for the Action method -
public string Search(string a)
{
JObject x = new JObject();
x.Add("Name", a);
return x.ToString();
}
It works fine when I use an Object (for example - Book) like so -
public string Search(Book a)
{
JObject x = new JObject();
x.Add("Name", a.Name);
return x.ToString();
}
In that case, the book's name gets de-serialized just fine as I would expect. The class definition for the Book class -
public class Book
{
public int ID { get; set; }
public string Name { get; set; }
}
Can somebody please advise what I'm doing wrong? Is there no way to take in a string and then de-serialize? I'd like to be able to take in JSON without having to use an Object
As for as understand you want pass entire of request body to a string without any binding so you could handle passed string data with your desired way.
To aim this purpose simply write your own model binder:
public class RawBodyBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if(typeof(string)!=bindingContext.ModelType)
return null;
using (var s = new StreamReader(controllerContext.HttpContext.Request.InputStream))
{
s.BaseStream.Position = 0;
return s.ReadToEnd();
}
}
}
And in you action method assign your binder to desired parameter:
public string MyAction([ModelBinder(typeof(RawBodyBinder))]string json)
{
}
But MVC has own JSON model binder as well if your data is a standard JSON and in request body with Content-Type: application/json header you could use MVC's JSON model binder to activate JSON model binder simply add following line in Global.asax.cs file:
protected void Application_Start()
{
// some code here
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
}
The first thing in asp.net mvc to post a data is to decorate the method with this attribute [Httpost]
it's mean passing a string
should look like
[HttpPost]
public string Search(string a){
// your code
}
The default value is [HttpGet] that get parameters from url. For Post request you need to.
Edit:
And look the answer from vinayan
with jquery:
$.ajax({
method: "POST",
url: "Home/Search",
data: {'a': 'yourstring'}
})
The name of the parameter you send is used for the de-serialization. So in in this case, "a" should be part of json.
public string Search(string a)
so you will have to use,
$.ajax({
method: "POST",
url: "Home/Search",
data: {'a': 'yourstring'}
})

ASP.NET MVC: How to create an action filter to output JSON?

My second day with ASP.NET MVC and my first request for code on SO (yep, taking a short cut).
I am looking for a way to create a filter that intercepts the current output from an Action and instead outputs JSON (I know of alternate approaches but this is to help me understand filters). I want to ignore any views associated with the action and just grab the ViewData["Output"], convert it to JSON and send it out the client. Blanks to fill:
TestController.cs:
[JSON]
public ActionResult Index()
{
ViewData["Output"] = "This is my output";
return View();
}
JSONFilter.cs:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/*
* 1. How to override the View template and set it to null?
* ViewResult { ViewName = "" } does not skip the view (/Test/Index)
*
* 2. Get existing ViewData, convert to JSON and return with appropriate
* custom headers
*/
}
Update: Community answers led to a fuller implementation for a filter for JSON/POX.
I would suggest that what you really want to do is use the Model rather than arbitrary ViewData elements and override OnActionExecuted rather than OnActionExecuting. That way you simply replace the result with your JsonResult before it gets executed and thus rendered to the browser.
public class JSONAttribute : ActionFilterAttribute
{
...
public override void OnActionExecuted( ActionExecutedContext filterContext)
{
var result = new JsonResult();
result.Data = ((ViewResult)filterContext.Result).Model;
filterContext.Result = result;
}
...
}
[JSON]public ActionResult Index()
{
ViewData.Model = "This is my output";
return View();
}
You haven't mentioned only returning the JSON conditionally, so if you want the action to return JSON every time, why not use:
public JsonResult Index()
{
var model = new{ foo = "bar" };
return Json(model);
}
maybe this post could help you the right way. The above post is also a method

Categories