In my MVC3 solution I'm wondering how to move the logic that returns Json out of the controller and into the service layer. Say I have the following action in my controller to get the Json needed for a JQueryUI autocomplete control:
public JsonResult ClientAutocompleteJSON(string term)
{
NorthwindEntities db = new NorthwindEntities();
var customers = db.Customers
.Where(c => c.ContactName.Contains(term))
.Take(25)
.Select(c => new
{
id = c.CustomerID,
label = c.ContactName,
value = c.ContactName
});
return Json(customers, JsonRequestBehavior.AllowGet);
}
How would I move this into the service layer? I would prefer not to reference System.Web.MVC in my service layer. I've also thought of returning the customers but I'm not sure how to return the anonymous type - would I have to create a class?
I would not couple your service implementation to a specific (UI) format. It would be better to return a strongly typed customer object and then format this how you want within your Action method.
// Service method
public IEnumerable<Customer> FindCustomers(string term) {
NorthwindEntities db = new NorthwindEntities();
return db.Customers
.Where(c => c.ContactName.Contains(term))
.Take(25)
.ToList();
}
// Action method
public JsonResult ClientAutocompleteJSON(string term) {
var customers = customerService.FindCustomers(term)
.Select(c => new
{
id = c.CustomerID,
label = c.ContactName,
value = c.ContactName
});
return Json(customers, JsonRequestBehavior.AllowGet);
}
This code is much more reusable - for example, you could use the same service method to provide a simple HTML search form.
Create a DTO object: http://martinfowler.com/eaaCatalog/dataTransferObject.html
I know about a feature in Ruby on Rails, there you can define that your method is capable of returning JSON or XML or HTML based on client preference, it will be a good feature if you can find a library that can do this for you. It could be an aspect which by dynamic proxifying your services can do.
Related
services.AddHeaderPropogation(o =>
{
o.Headers.Add("Id")
o.Headers.Add("Id", context => {
return new StringValues(Guid.NewGuid().ToString())
});
});
The above code helps me to create a header called id if it doesnt exist with a new guid and if it exists, it would just use the value. This is using Microsoft Header Propogation nuget package. And it works.
But now i have a requirement to add this to Azure Application insights, but the standard way of doiing it only works when the incoming request has headers. If the new GUID is created, it doesnt trigger the ITelemetryInitializer call.
Because for adding Telmetry custom values, we have a class which inherits ITelemtryInitializer and inside that i do call to Request.Headers like below:
var requestTelemetry = telemetry as RequestTelemetry
if(context.Request.Headers.TryGetValue(id, out var value))
requestTelemtry.Properties[id] = value.ToString()
But the above line is never triggered since the Request.Headers never had this id. This id will be created only by the middleware when the api calls the next service.
So my question, is there a way to call the telemetry classes from the Startup> ConfigfureServices and inside the HeaderPropogation code, so that as soon as the new GUID is created, i can add it to telemtry. All the examples of adding to telemetry shows either from controller or DI. How to call it from the Startup itself ?
Or is there a better way to achieve the same ?
Let me post the solution I found. I didnt need to have a telemetryinitializer class to populate the guid in another class. I wanted it to be added as soon as we create it, so this is how i modified th header propogiation code in services in startup.
services.AddHeaderPropagation(options =>
{
var correlationId = "YourId";
options.Headers.Add(correlationId, context => {
var requestTelemetry = context.HttpContext.Features.Get<RequestTelemetry>();
if (context.HttpContext.Request.Headers.TryGetValue(correlationId, out var value))
{
requestTelemetry.Properties[correlationId] = value.ToString();
return value.ToString();
}
else
{
var guidId = Guid.NewGuid().ToString();
requestTelemetry.Properties[correlationId] = guidId;
return new StringValues(guidId);
}
});
});
Key for me has been the realization i can get RequestTelemetry anywhere we want in the code with this properties option.
var requestTelemetry = context.HttpContext.Features.Get<RequestTelemetry>();
when I call my API Webservice its returns an empty array.
In my Header request, i have only a jwt token for authenticating
In Angular:
getSheets(): Observable<Sheet[]> {
return this.http.get(this.config.apiUrl + '/api/SheetsRelationAPI', this.jwt())
.map(this.extractData)
.do(data => console.log('SheetsData:', data)) // debug
.catch(this.handleError);
In Asp.net MVC 5:
[HostAuthentication("bearer")]
[System.Web.Http.Authorize]
public class SheetsRelationAPIController : ApiController
{
private GSheetsContext db = new GSheetsContext();
// GET: api/SheetsRelation
[ResponseType(typeof(SheetsRelationView))]
public IQueryable<SheetsRelationView> GetSheetsRelation()
{
var claims = (User.Identity as System.Security.Claims.ClaimsIdentity).Claims;
var username = "";
foreach (var claim in claims)
if (claim.Type.ToString() == "sub")
{
username = claim.Value.ToString();
}
//var tasks = from tsk in db.SheetsRelation.Include(s => s.SheetsContent.id )
//select tsk;
var sheetsRelation = db.SheetsRelationView.Where(jt => jt.Username == username);
return sheetsRelation;
}
}
UPDATE 1:
It seems it's worked in PostMan and I have a JSON in response But in Angular, i haven't any JSON in response.
Three things you may wish to try -
Not related to this issue, but I always decorate my APIs with the specific http method, to ensure there isnt any confusion on my part - [HttpGet] in this case.
Refactor your API class so that it doesnt have direct dependencies on GSheetsContext and User.Identity (make yourself an injectable service to handle that, so that you can mock the behavior.)
Unit test the controller method with mocked dependencies, so that you can be sure that your controller is behaving as it is expected to.
Edit, if that sounds like too much work
Comment out your existing controller logic and put a stub method there that just returns something like return db.SheetsRelationView.Create()
If that works, then you know your issue is not with your API, and is in the logic. Then refer back to steps 2 & 3 above ;)
I don't know if my google skills are diminishing or what but I can't seem to figure out how to consume a local api. This may be best explained with sample code...
So I have a simple api
public class FooApiController : Controller
{
public IActionResult GetFoo(int id)
{
if (id == 0)
return BadRequest();
var data = ... do db access
return Ok(data);
}
}
and a view controller
public class FooController : Controller
{
public IActionResult Foo()
{
var api = new FooApiController();
var data = api.GetFoo(1);
ViewBag.Data = data;
return View();
}
}
So in the above view controller I call the api to get the data needed. However, being that the api controller returns an IActionResult, ViewBad.Data ends up being an IActionResult object. So how do I change the above to check the StatusCode of the api call, handle errors if need be, and if not... put just the data into the ViewBag, instead of the entire result object.
Every sample I have found seems to have the view controller return a view that then uses an ajax call to get the data. While I understand and could easily do that, I don't like the idea of making 2 round trips to the server when I don't need to.
You are doing it wrong.
If you want to reuse the code among multiple controllers, then it is better to move it from the GetFoo method and put it into a shared class and access it from everywhere else.
If you want to call it from a view through REST, then call it using $.ajax
ex:
$.ajax('FooApi/GetFoo/5',function(data){alert(data);});
If you want to access it from another C# client, then use the HttpClient class, ex:
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.PostAsJsonAsync("api/FooApi/GetFoo", 3);
response.EnsureSuccessStatusCode();
i am working on an asp.net mvc web application. and i have the following repository method,, where i will be passing the .Include() dynamically :-
public async Task<SecurityRole> FindSecurityRole(int id,string path="")
{
return await context.SecurityRoles.Include(path).SingleOrDefaultAsync(a2 => a2.SecurityRoleID == id);
}
now inside my controller i want to call the above method as follow:-
await uniteofwork.SecurityRoleRepository.FindSecurityRole(id.Value,)
but i am not sure what are the apporachies i can follow to pass the properties ?
Thanks
You can chain calls to things like Include over multiple lines by storing the result in a variable. Nothing will actually hit your database until you call an evaluating expression like SingleOrDefaultAsync here.
var query = context.SecurityRoles;
foreach (var include in path.Split(',', StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(include);
}
return await query.SingleOrDefaultAsync(a2 => a2.SecurityRoleID == id);
Splitting the string allows you to pass multiple include hierarchies at once, comma-delimited.
I am writing an MVC 4 application, and using Entity Framework 4.1. I have a validation question which I cannot seem to find the answer to.
Essentially, I have an Entity (object) called "Product" which contains a field "Name", which must follow strict naming conventions which are defined in a separate Entity called "NamingConvention". When the user enters a value, the system needs to check it against the rules established in the NamingConvention entity, and return an error if need be.
Where should this validation be done, and how? I need to check the NamingConvention entity when doing the validation, which means I would need a database context since I'm referencing a different entity. Is there any validation method which won't require me to create a new context? I was thinking of doing the validation in the Controller, since it already creates a data context, but this doesn't seem like the right place to do it.
Thanks for any help!
I have done things like this using a JQuery post (ajax) call from the webpage where the name is being entered. You then post (the value of name) to a method on your controller which can return a JSON value that contains a flag saying if the validation passed and also a message that you want to return to your user. For example :
Javascript in webpage :
$("#name").change(function () {
var nameVal = $(this).val();
$.post(getRoot() + "/NameController/ValidateName", { name: nameVal },
function (data) {
if (data.valid == "true") {
alert("A valid name was chosen");
} else
{
alert(data.message);
}
}, "json");
});
Controller (NameController) Code :
[HttpPost]
public ActionResult ValidateName(string name)
{
// actual validation carried out in a static utility class (Utils.IsNameValid)
// if you are loading the same validation rules from your table each time
// consider caching the data in the application cache or a static List.
bool nameIsValid = Utils.IsNameValid(name, out string ErrorMessage);
JsonResult result = new JsonResult();
result.Data = new { valid = (nameIsValid "true" : "false"), message = ErrorMessage };
return result;
}
I'm using EF 5 but believe you can use this method ... apologies in advance if I'm misleading you with this answer.
You could do the validation within your context (or a context decorator)
public override int SaveChanges()
{
var products = this.GetChangedProducts();
foreach (var product in products)
{
this.ValidateName(product);
}
return base.SaveChanges();
}
private IEnumerable<Product> GetChangedProducts()
{
return (
from entry in _context.ChangeTracker.Entries()
where entry.State != EntityState.Unchanged
select entry.Entity)
.OfType<Product>();
}
private void ValidateName(Product product)
{
//validate here
}