I have just started working with Web Api OData and I find myself wanting to do something that I was able to do a standard ApiController, that is get an object from a field other than the Id field. I know it is standard to do this for example when getting an object based on it's Id :
[Queryable]
public SingleResult<Group> GetGroup([FromODataUri] int key)
{
return SingleResult.Create(db.Groups.Where(group => group.GroupId == key));
}
But if I want to get group by groupName field for example, what would I do? I have tried this as something similar worked with an ApiController :
Controller :
public Group GetGroup([FromODataUri] string groupName)
{
var group = _repository.GetGroupByGroupName(groupName);
if (group == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return group;
}
Repository :
public Group GetGroupByGroupName(string groupName)
{
Group group = (from u in _db.Groups
where u.GroupName == groupName
select u).FirstOrDefault();
return group;
}
My WebApiConfig looks like this :
public static void Register(HttpConfiguration config)
{
config.EnableCors();
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
// OData
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<City>("Cities");
modelBuilder.EntitySet<Stage>("Stages");
modelBuilder.EntitySet<Team>("Teams");
modelBuilder.EntitySet<Fixture>("Fixtures");
modelBuilder.EntitySet<Roster>("Rosters");
modelBuilder.EntitySet<Standing>("Standings");
modelBuilder.EntitySet<Group>("Groups");
var model = modelBuilder.GetEdmModel();
config.Routes.MapODataRoute("ODataRoute", "odata", model);
}
I want to be able to get a group object based on a groupname using odata/Groups/GroupName, but what I currently have does not work. How would I do this, or am I approaching this from totally the wrong direction?
You are already using QueryableAttribute so if you define something like:
[Queryable]
public IQueryable<Group> Get()
{
// Returns all groups as a queryable (do not execute query here!)
}
You can then access this with an ajax request and any odata query, like:
http://server/Api/Group?$filter=GroupName eq 'test'
The query is built on the client, not restricted by server parameters.
Let's say you STILL need to call a custom function. OData V4 allows that. The question is a bit old but I believe it is still valid today.
First, you need to specify a Namespace to the ODataConventionModelBuilder since OData V4 needs this for actions and functions:
var builder = new ODataConventionModelBuilder();
builder.Namespace = "Default";
Be aware that this might result in IIS giving 404 because the dot is interpreted by IIS. To prevent this, add the following to your Web.config:
<system.webServer>
<handlers>
<clear/>
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="/*"
verb="*" type="System.Web.Handlers.TransferRequestHandler"
preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
then, you need to declare the function too:
builder.EntitySet<Group>("Groups").EntityType
.Collection
.Function("GetGroupByName")
.ReturnsFromEntitySet<Group>("Groups")
.Parameter<string>("name");
Notice the function is declared on the COLLECTION, so it's bound to it. Then the ODataController method:
[ODataRoute("Groups/Default.GetGroupByName(name={name})")]
[HttpGet]
public IHttpActionResult GetGroupByName([FromODataUri]string name)
{
return Ok(new Group() or whatever);
}
Here the ODataRoute annotation is necessary only if you use a parameter. It defines in order: the entityset, the namespace, the method and the parameters.
You can then use:
http://localhost:xxxx/api/Groups/Default.GetByGroupName(name='sausage')
Also, see this SO answer to pass multiple parameters
Related
I'm working on a dotnet mvc5 application. Here's a function from my api of customer controller
public IHttpActionResult GetCustomers()
{
var customerDtos = _context.Customers.ToList().Select(Mapper.Map<Customer, CustomerDto>);
return Ok(customerDtos);
}
I need to add "TYPEAHEAD" plugin to my application. The video series/instructor I'm following says to make the function code change to
public IHttpActionResult GetCustomers(string query = null)
{
var customersQuery = _context.Customers
.Include(c => c.MembershipType);
if (!String.IsNullOrWhiteSpace(query))
customersQuery = customersQuery.Where(c => c.Name.Contains(query));
var customerDtos = customersQuery
.ToList()
.Select(Mapper.Map<Customer, CustomerDto>);
return Ok(customerDtos);
}
in order to make "TypeAhead" plug in work on my view.
The only problem is previously while creating customers I didn't feel the need to add "MembershipType" class to my customer. So how do I use the new code without MembershipType. Is there any other attribute I can replace it with? Name, ID etc.
.Include(c => c.MembershipType);
essentially means that you also want to include the 'child' collection of MembershipType
See here for more information on Loading Related Entities
For your case, you can simply omit this.
public IHttpActionResult GetCustomers(string query = null)
{
var customersQuery = _context.Customers;
if (!String.IsNullOrWhiteSpace(query))
customersQuery = customersQuery.Where(c => c.Name.Contains(query));
var customerDtos = customersQuery
.ToList()
.Select(Mapper.Map<Customer, CustomerDto>);
return Ok(customerDtos);
}
You don't need to replace it with anything.
customersQuery is then an IQueryable<Customer> which the rest of this code can append Where clause to.
It is not executed against the database until the ToList call.
I have an ASP.NET MVC Razor website which is supposed to be live in multiple countries having different cultures and hence the languages. My Development team is concerned about english only and whole text on UI pages is also written in plain english. I want this english text to be converted into culture specific language. I'm using resource file to manage the strings on my website.
One way is to create multiple resource files based on each language and then using each file based on specific culture. This thing needs to be managed manually. If someone has done this thing please come up with any reference or any sample code for this implementation.
If there is any way where I can automate this thing then it will be best way to go for a multi lingual website. Like as culture can be easily detected by user IP address and based on the culture I should be able to convert all english based text into current culture specific language.
1. Automatically Use User-Culture
Have the user culture be set automatically on the current Thread/HttpContext. In your Web.Config:
<system.web>
...
<globalization enableClientBasedCulture="true" culture="auto" uiCulture="auto" />
...
</system.web>
2. Helper Function
Introduce a global method that will translate input text using the appropriate resource:
public static class Resources
{
public static string GetResource(string key, params object[] data)
{
if (String.IsNullOrEmpty(key))
return key;
// the actual call to your Resources
var res = Texts.ResourceManager.GetString(key.ToUpper(), Thread.CurrentThread.CurrentUICulture);
if (String.IsNullOrEmpty(res))
return data != null && data.Length > 0 ? String.Format(key.ToUpper(), data) : key;
if (data != null && data.Length > 0)
return String.Format(res, data);
return res;
}
}
The method also lets you pass an additional (optional) parameters to be used in a String.Format way. For example:
// in your Resource file (Texts.es.resx)
GREETING_TEXT: "Hola amigo {0}, el tiempo es {1}"
// invocation
Resources.GetResource("GREETING_TEXT", "Chaim", DateTime.Now);
3. Controller Helper:
Introducing a _ methods that will let you translate texts quickly in the Controller:
public class BaseController : Controller
{
public string _(string key, params object[] data)
{
return Resources.GetResource(key, null, data);
}
}
In your controller you have to make sure you're inheriting your BaseController and use it as follows:
public HomeController : BaseController:
{
public ActionResult GreetMe()
{
var msg = _("GREETING_TEXT", this.User, DateTime.Now);
return Content(msg);
}
}
4. Razor Helper
And for your Razor pages:
// non-generic version (for model-less pages)
public abstract class BaseWebPage : WebViewPage
{
public string _(string key, params object[] data)
{
return Resources.GetResource(key, null, data);
}
}
// generic version (for model defined pages)
public abstract class BaseWebPage<T> : WebViewPage<T>
{
public string _(string key, params object[] data)
{
return Resources.GetResource(key, null, data);
}
}
Now we have to set this new base WebPage as the base type for our pages in ~/Views/Web.Config:
<system.web.webPages.razor>
...
<pages pageBaseType="Full.NameSpace.ToYour.BaseWebPage">
...
</system.web.webPages.razor>
(If you're using Areas, you'll have to modify each ~/Areas/AREA_NAME/Views/Web.Config as well)
You can now use it in your Razor pages:
<h1>#_("HELLO")</h1>
I am a newbie in asp mvc, and would like to define links in views to image/content folder in a way so I don't have to change each link if a image folder changes.
Is is possible using ActionLink and routing, bundling or there is a better way to achieve this.
I could not find a good example anywhere so I did not try anything any coding far.
I am thinking of storing a fixed path somewhere, but is that really a mvc type solution?
There are a number of ways you could do this. Here's one approach to extend the Url.Content() method.
1. Create an extension method
We'll called it Virtual().
namespace TestApp.Extensions
{
public static class UrlHelperExtensions
{
private const string _settingPattern = "path:{0}";
private const string _regexPattern = #"\{\w+\}";
public static string Virtual(this UrlHelper helper, string url)
{
Regex r = new Regex(_regexPattern);
var matches = r.Matches(url);
if (matches.Count == 0) return url;
var sb = new StringBuilder(url);
var keys = WebConfigurationManager.AppSettings.AllKeys;
foreach (var match in matches)
{
string key = match.ToString().TrimStart('{').TrimEnd('}');
string pattern = string.Format(_settingPattern, key);
foreach (var k in keys)
{
if (k == pattern)
{
sb.Replace(match.ToString(), WebConfigurationManager.AppSettings.Get(k));
}
}
}
return helper.Content(sb.ToString());
}
}
}
2. Add settings to the main Web.config
Freely add any paths you want.
<add key="path:images" value="~/Content/images" />
<add key="path:scripts" value="~/scripts" />
3. Add the namespace to the Web.config of your views directory
<namespaces>
<add namespace="TestApp.Extensions"/>
</namespaces>
4. Use the new method
#Url.Virtual("{images}/mypic.png")
Output:
/Content/images/mypic.png
You can now use Virtual() where you would Content().
This solution is arguably excessive, but it is comprehensive.
I am using Entity framework 5 and using repository pattern. Say I got these entities Customer, Files, Images, Tasks, Invoice, User.
Each entity (apart from Customer) has a foreign key of Customer. When a user logs in I store the customerid in session (aps.net mvc). What I want is any CRUD taken on all entities to be limited to the customer who's user is logged in. e.g I can't afford to delete a Task belonging to customer 1 to be deleted by user who is from customer 2.
Is adding an argument of customerid for each method of repositories the best way to achieve this or are there any better/clever ways of doing it?
Tricky to give a definitive answer but you could make it a bit more extensible by implementing higer order functions, like this:
public interface IRepository<T>
{
public T GetBy(Expression<Func<T, bool>> query)
}
public class FileRepository : IRepository<File>
{
public File GetBy(Expression<Func<T, bool>> query)
{
using(var context = new FilesContext())
{
return context.Files.Where(query).FirstOrDefault();
}
}
}
public class SomeController
{
private IRepository<File> _repo;
public SomeController(IRepository<File> repo)
{
_repo = repo;
}
public ActionResult Index()
{
var model = _repo.GetBy(f => f.CustomerId == Session.Whatever.CustomerId);
return View(model);
}
}
This way you can vary the search query when required, rather than tie yourself in to using a hardcoded customer id property. For example, if you wanted to get the File object by the FileID, not the CustomerID, then:
var model = _repo.GetBy(f => f.FileId == someId);
and that's the only part of the code that needs to change.
Some really good info on Higher Order functions and functional programming in C# here: http://www.codeproject.com/Articles/375166/Functional-programming-in-Csharp
Edit:
You might be able to isolate the "Always use the customer ID when hitting DB" into a repository of it's own, using a decorator style pattern, thus: (massive disclaimer - I haven't tested this, but something along these lines should work)
public class SpecialFileRepo : IRepository<File>
{
private readonly IRepository<File> _baseRepo;
public SpecialFileRepo(IRepository<File> baseRepo)
{
_baseRepo = baseRepo;
}
public SpecialFileRepo() : this(new FileRepository())
{
}
public File GetBy(Expression<Func<File, bool>> query)
{
var parameters = query.Parameters;
var newParam = Expression.Parameter(typeof (File), "f");
var additionalQuery = Expression.AndAlso(query.Body,
Expression.Equal(
Expression.PropertyOrField(newParam, "CustomerId"),
Expression.Constant(HttpContext.Current.Session["customerId"])));
var newQuery = query.Update(additionalQuery, parameters);
return _baseRepo.GetBy(newQuery);
}
}
Then anything that's talking to a repository, as far as it's concerned, it's just a base repository, but this class is sitting in between and always grafting the "customerid = sessionwhatever" expression onto what finally gets passed to the database. And of course, anything that only cares about using the base repository, can still do so.
I feel a bit absurd asking this but I can't find a way to get parameters for a get request at
/api/foo?sort=name for instance.
In the ApiController class, I gave a public string Get(). Putting Get(string sort) makes /api/foo a bad request. Request instance in the ApiController is of type System.Net.Http.HttpRequestMessage. It doesn't have a QueryString or Parameters property or anything.
The ApiController is designed to work without the HttpContext object (making it portable, and allowing it to be hosted outside of IIS).
You can still access the query string parameters, but it is done through the following property:
Request.GetQueryNameValuePairs()
Here's an example loop through all the values:
foreach (var parameter in Request.GetQueryNameValuePairs())
{
var key = parameter.Key;
var value = parameter.Value;
}
You could just use
HttpContext.Current.Request.QueryString
Here's an example that gets the querystring q from the request and uses it to query accounts:
var q = Request.GetQueryNameValuePairs().Where(nv => nv.Key =="q").Select(nv => nv.Value).FirstOrDefault();
if (q != null && q != string.Empty)
{
var result = accounts.Where(a=>a.Name.ToLower().StartsWith(q.ToLower()));
return result;
}
else
{
throw new Exception("Please specify a search query");
}
This can be called then like this:
url/api/Accounts?q=p
Get all querystring name/value pairs into a variable:
IEnumerable<KeyValuePair<string, string>> queryString = request.GetQueryNameValuePairs();
Then extract a specified querystring parameter
string value = queryString.Where(nv => nv.Key == "parameterNameGoesHere").Select(nv => nv.Value).FirstOrDefault();
You can also use the following
var value = request.GetQueryNameValuePairs().Where(m => m.Key == "paramName").SingleOrDefault().Value;
if we have a proper model for that request
for example
public class JustModel
{
public int Id {get;set;}
public int Age {gets;set;}
}
and query like this
/api/foo?id=1&Age=10
You could just use [FromUri] attribute
For example
public IHttpActionResult GetAge([FromUri] JustModel model){}
You're trying to build an OData webservice? If so, just return an IQueryable, and the Web API will do the rest.
Adding a default value does the job:
public string Get(string sort="")